package top.jfunc.token.mem;

import top.jfunc.common.thread.ThreadUtil;
import top.jfunc.common.utils.CollectionUtil;
import top.jfunc.common.utils.MapUtil;

import java.time.Clock;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 对map的key进行扫描，删除过期的，模拟redis
 * @author xiongshiyan at 2021/5/13 , contact me with email yanshixiong@126.com or phone 15208384257
 */
public class KeyExpireMap<K,V> extends AbstractMap<K,V> implements Map<K,V>{
    private Map<K,ValueWithDate<V>> delegate;
    private ScheduledExecutorService scheduledExecutorService;

    @SuppressWarnings("unchecked")
    public KeyExpireMap(Map<K, V> delegate, long expire) {
        try {
            this.delegate = delegate.getClass().newInstance();
            if (MapUtil.notEmpty(delegate)){
                long l = System.currentTimeMillis();
                delegate.forEach((k,v)->this.delegate.put(k,new ValueWithDate(v,l)));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }


        //秒变毫秒
        final long expireInMs = expire*1000;
        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleWithFixedDelay(()->{
            //遍历所有查看是否过期，过期就删除
            long l = Clock.systemDefaultZone().millis();
            Set<K> ks = this.delegate.keySet();
            Set<K> keyToRemove = new HashSet<>(ks.size());
            for (K k : ks) {
                if(l-this.delegate.get(k).timestamp>expireInMs){
                    keyToRemove.add(k);
                }
            }
            for (K k : keyToRemove) {
                this.delegate.remove(k);
            }

        },0,1, TimeUnit.SECONDS);
    }

    @Override
    public boolean containsValue(Object value) {
        for (Entry<K, ValueWithDate<V>> entry : this.delegate.entrySet()) {
            if (value.equals(entry.getValue().v)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public V get(Object key) {
        ValueWithDate<V> value = delegate.get(key);
        return null== value ? null : value.v;
    }

    @Override
    public V put(K key, V value) {
        ValueWithDate<V> put = this.delegate.put(key, new ValueWithDate<>(value, System.currentTimeMillis()));
        return null == put ? null : put.v;
    }

    @Override
    public V remove(Object key) {
        ValueWithDate<V> remove = this.delegate.remove(key);
        return null == remove ? null : remove.v;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        if(MapUtil.notEmpty(m)){
            long l = System.currentTimeMillis();
            m.forEach((k,v)->{
                this.delegate.put(k,new ValueWithDate<>(v,l));
            });
        }
    }

    @Override
    public void clear() {
        this.delegate.clear();
    }

    @Override
    public Set<K> keySet() {
        return this.delegate.keySet();
    }

    @Override
    public Collection<V> values() {
        Collection<ValueWithDate<V>> values = this.delegate.values();
        if(CollectionUtil.isEmpty(values)){
            return Collections.emptyList();
        }
        Set<V> objects = new HashSet<>(values.size());
        for (ValueWithDate<V> value : values) {
            objects.add(value.v);
        }
        return objects;
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        Set<Entry<K, ValueWithDate<V>>> entrySet = this.delegate.entrySet();
        if(CollectionUtil.isEmpty(entrySet)){
            return Collections.emptySet();
        }
        Set<Entry<K, V>> objects = new HashSet<>(entrySet.size());
        for (Entry<K, ValueWithDate<V>> entry : entrySet) {
            objects.add(new SimpleEntry<>(entry.getKey(), entry.getValue().v));
        }
        return objects;
    }

    private static class ValueWithDate<V>{
        private V v;
        private long timestamp;

        public ValueWithDate(V v, long timestamp) {
            this.v = v;
            this.timestamp = timestamp;
        }

        @Override
        public String toString() {
            return "v=" + v ;
        }
    }

    private static class SimpleEntry<K,V> implements Map.Entry<K,V>{
        private Object k;
        private Object v;

        SimpleEntry(K k, V v) {
            this.k = k;
            this.v = v;
        }

        @Override
        public K getKey() {
            return (K)k;
        }

        @Override
        public V getValue() {
            return (V)v;
        }

        @Override
        public V setValue(V value) {
            Object v = this.v;
            this.v=value;
            return (V)v;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            SimpleEntry<?, ?> that = (SimpleEntry<?, ?>) o;

            if (k != null ? !k.equals(that.k) : that.k != null) return false;
            return v != null ? v.equals(that.v) : that.v == null;
        }

        @Override
        public int hashCode() {
            int result = k != null ? k.hashCode() : 0;
            result = 31 * result + (v != null ? v.hashCode() : 0);
            return result;
        }
    }

    public static void main(String[] args) {
        HashMap<Object, Object> hashMap = new HashMap<>();
        KeyExpireMap<Object, Object> x = new KeyExpireMap<>(hashMap, 2);

        for (int i = 0; i < 100; i++) {
            x.put(i+"", i+"");
            System.out.println(x.size() + ":" + x);
            ThreadUtil.sleeps(1);
        }
    }
}
